#  Copyright (c) 2010
#  The Regents of the University of Michigan
#  All Rights Reserved

#  Permission is granted to use, copy, create derivative works, and
#  redistribute this software and such derivative works for any purpose,
#  so long as the name of the University of Michigan is not used in
#  any advertising or publicity pertaining to the use or distribution
#  of this software without specific, written prior authorization. If
#  the above copyright notice or any other identification of the
#  University of Michigan is included in any copy of any portion of
#  this software, then the disclaimer below must also be included.

#  This software is provided as is, without representation or warranty
#  of any kind either express or implied, including without limitation
#  the implied warranties of merchantability, fitness for a particular
#  purpose, or noninfringement.  The Regents of the University of
#  Michigan shall not be liable for any damages, including special,
#  indirect, incidental, or consequential damages, with respect to any
#  claim arising out of or in connection with the use of the software,
#  even if it has been or is hereafter advised of the possibility of
#  such damages.

# K=8, not 20!
from libtorrent import bdecode, bencode

import collections
import socket
import struct

class SerializationError(Exception): pass

Contact = collections.namedtuple('Contact', ['id', 'address'])

RequestPing = collections.namedtuple('RequestPing', ['transaction_id'])
ReplyPing = collections.namedtuple('ReplyPing', ['transaction_id'])

RequestFindNode = collections.namedtuple('RequestFindNode', ['transaction_id',
                                                             'target_id'])
ReplyFindNode = collections.namedtuple('ReplyFindNode', ['transaction_id',
                                                         'contacts'])

# GetPeers; called FindValue for uniformity.
RequestFindValue = collections.namedtuple('RequestFindValue',
                                          ['transaction_id',
                                           'info_hash'])

# XXX: Ignore FindValue replies. We'll probably ignore requests too.

COMPACT_NODE_INFO = struct.Struct('!20sIH')


def encode_ping_request(request, sender_id):
  return bencode({'t': 'p' + request.transaction_id, 'y': 'q', 'q': 'ping',
                  'a': {'id': sender_id}})


def encode_find_node_request(request, sender_id):
  return bencode({'t': 'f' + request.transaction_id, 'y': 'q', 'q': 'find_node',
                  'a': {'id': sender_id, 'target': request.target_id}})


def decode_query(name, query_args, txid, handler, sender_contact,
                 sender_address):
  try:
    if name == 'ping':
      query = RequestPing(txid)
      handler.handle_ping_request(query, sender_contact, sender_address)
    elif name == 'find_node':
      query = RequestFindNode(txid, query_args['target'])
      handler.handle_find_node_request(query, sender_contact, sender_address)
    elif name == 'get_peers':
      pass  # XXX: Handlers not currently required to handle this.
      # query = RequestFindValue(txid, node_id, query_args['info_hash'])
      # handler.handle_find_value_request(query, *args)
    elif name == 'announce_peer':
      pass  # XXX: Not used by Peruze, so not implemented atm.
    else:
      raise SerializationError('Bad query name!')
  except KeyError:
    raise SerializationError('Bad query args!')


def decode_nodes_reply(nodes_str):
  nodes = []
  node_size = COMPACT_NODE_INFO.size
  while len(nodes_str) >= node_size:
    id, ip, port = COMPACT_NODE_INFO.unpack(nodes_str[:node_size])
    nodes.append(Contact(id, (socket.inet_ntoa(struct.pack('!I', ip)), port)))
    nodes_str = nodes_str[node_size:]
  return nodes


def decode_response(response, txid, handler, sender_contact, sender_address):
  typ = txid[0]
  txid = txid[1:]
  try:
    if typ == 'p':
      query = ReplyPing(txid)
      handler.handle_ping_reply(query, sender_contact, sender_address)
    elif typ == 'f':
      nodes = decode_nodes_reply(response['nodes'])
      query = ReplyFindNode(txid, nodes)
      handler.handle_find_node_reply(query, sender_contact, sender_address)
    # elif name == 'get_peers':
    #   pass  # XXX: Handlers not currently required to handle this.
    #   # query = RequestFindValue(txid, node_id, args['info_hash'])
    #   # handler.handle_find_value_request(query, *sender_address)
    # elif name == 'announce_peer':
    #   pass  # XXX: Not used by Peruze, so not implemented atm.
    else:
      raise SerializationError('Bad response txid tag!')
  except KeyError:
    raise SerializationError('Bad response args!')


def decode_packet(packet, handler, sender_address):
  try:
    pdict = bdecode(packet)
  except RuntimeError:
    pdict = None

  try:
    txid = pdict['t']
  except (KeyError, TypeError):
    raise SerializationError('No txid!')

  try:
    msg_type = pdict['y']
  except KeyError:
    raise SerializationError('No message type!')

  if msg_type == 'q':
    try:
      query_name = pdict['q']
      query_args = pdict['a']
      node_id = query_args['id']
    except KeyError:
      raise SerializationError('Bad query!')
    decode_query(query_name, query_args, txid, handler,
                 Contact(node_id, sender_address), sender_address)
  elif msg_type == 'r':
    try:
      response = pdict['r']
      node_id = response['id']
    except KeyError:
      raise SerializationError('Bad response: %s!' % pdict)
    try:
      decode_response(response, txid, handler,
                      Contact(node_id, sender_address), sender_address)
    except struct.error as e:
      raise SerializationError(e)
  elif msg_type == 'e':
    pass  # We don't bother with these silly "errors" right now.
    # try:
    #   err_code, err_msg = pdict['e']
    # except KeyError, ValueError:
    #   raise SerializationError('Bad error!')
    # handler.handle_error(...)
  else:
    raise SerializationError('Unknown message type!')
